package com.atguigu.aops;

import com.atguigu.annotations.MyRedisCache;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @Author: shenhao
 * @CreatTime: 2024/11/7 9:59
 * @Version: 1.0
 * 在线的实时的内容一定要使用redis实现
 */
@Aspect
@Component
@Slf4j
public class MyRedisCacheAspect {
    @Autowired
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(com.atguigu.annotations.MyRedisCache)")
    public void cachePointCut() {
    }

    @Around("cachePointCut()")
    public Object doCache(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;
        try {
            //1 获得重载后的方法名
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            //2 确定方法名后获得该方法上面配置的注解标签MyRedisCache
            MyRedisCache annotation = method.getAnnotation(MyRedisCache.class);
            //3 拿到了MyRedisCache这个注解标签，获得该注解上面配置的参数进行封装和调用
            String keyPrefix = annotation.keyPrefix();
            String matchValue = annotation.matchValue();
            log.info("keyPrefix:{},matchValue:{}", keyPrefix, matchValue);
            // matchValue = # id
            String name = matchValue.replace("#", "");
            //4 SpringEL 解析器
            ExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(matchValue);
            EvaluationContext context = new StandardEvaluationContext();
            //5 获得方法里面的形参个数
            Object[] args = joinPoint.getArgs();
            DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
            String[] parameterNames = discoverer.getParameterNames(method);
            for (int i = 0; i < parameterNames.length; i++) {
                System.out.println("获得方法里的参数名和值" + parameterNames[i] + "\t" + args[i]);
                if (name.equals(parameterNames[i])) {
                    context.setVariable(parameterNames[i], args[i].toString());
                }
            }
            //6 通过上述，拼接redis的最终key形式
            String value = expression.getValue(context, String.class);
            String redisKey = keyPrefix + ":" + value;
            log.info("redisKey:{}", redisKey);
            //7 先去redis里面查询看有没有
            result = redisTemplate.opsForValue().get(redisKey);
            if (result != null) {
                log.info("-------redis里面有数据,我直接返回, 不打扰MySQL:{}", result);
                return result;
            }
            //redis中没有数据, 我进行业务逻辑
            //8 redis里面没有，去找msyql查询或叫进行后续业务逻辑
            //-------aop精华部分,才去找findUserById方法干活
            result = joinPoint.proceed();
            if (result != null) {
                log.info("------redis里面无, 还需要把结果存入redis一次，缓存补偿: {}", result);
                redisTemplate.opsForValue().set(redisKey, result);
            }
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        return result;
    }
}
